home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Tools & Utilities
/
Collection of Tools and Utilities.iso
/
c
/
romize.zip
/
TCROM.DOC
< prev
Wrap
Text File
|
1989-02-02
|
14KB
|
344 lines
WRITING ROMABLE CODE IN TURBO C:
Updated 2/2/89 for Turbo C 2.0 by the author.
I recently completed a consulting project to design a
special-purpose controller using an embedded 8088 with firmware
in ROM. To create the firmware, I developed a scheme for
generating ROMable code from Turbo C.
The scheme utilizes the following:
1. Rules for writing C code.
2. ROMIZE.C, a program to post-process the Turbo C Compiler
assembler output.
3. INSJUMP.C, a program to patch the ROM binary code to
insert a restart jump instruction.
4. BINTOHEX.C, a program to produce Intel-format files for
downloading ROM images to an EPROM programmer.
5. C0ROMX.ASM, a replacement for C0.ASM, the startup code.
In addition, the scheme requires the Turboc C command-line
compiler, TCC.EXE; an assembler, Microsoft Version 3.0 or later,
or equivalent (I use Microsoft V5.0); TLINK or MS-Link; and
EXE2BIN (more about EXE2BIN later).
The scheme involves the following steps:
1. C code is written following a few rules.
2. TCC is used to translate the C code to assembly.
3. ROMIZE is used to perform certain modifications to the
assembly code.
4. The modified assembly code is assembled into a .OBJ file.
5. The .OBJ file is linked with the C0ROMX.OBJ startup code
and with other .OBJ files (if needed; these may be from C code
that has gone through the four above steps, or routines
originally coded in assembly). This produces a .EXE file.
6. EXE2BIN converts the .EXE into a binary ROM image.
7. INSJUMP inserts the reset jump into the ROM image.
8. If necessary, the binary ROM image is converted into
whatever format the particular EPROM programmer requires. (Mine
is a plug-in card for the PC which takes binary format directly,
so I don't have to do this step.)
All these steps can be run in a .BAT or MAKE file, making the
conversion of the original C code to ROM code almost automatic.
RULES AND ASSUMPTIONS:
The rules and assumptions for writing ROMable code using this
scheme are as follows:
1. The Compact memory model (small code, large data) is used.
This results in a maximum of 64K of code and initialized static
variables (i.e. constants) but up to 1 MB of Data space.
2. Initialized static variables are considered constants, and
may not be changed during program execution because they are
stored in ROM.
3. Variables defined as "extern" are considered constants.
Such variables would be used for font tables, for example. True
"variables" (which change during program execution) may not be
defined in one C module and declared as "extern" in another. The
only means of inter-module variable passing is through function
parameters and return values (a good programming practice
anyway). IT IS OK to define variables in a C module and reference
these as EXTRN's in assembler-coded modules, however.
4. Most of the Runtime Library routines may NOT be used,
because they have been compiled with the wrong segment groupings
for the ROM environment. Many of the library routines are not
usable, anyway, because they pertain to the DOS environment and
use system facilities that are not available in the ROM
environment. Library routines that are needed will have to be
recreated, either in C or assembly. You may want to order the
Turbo C Runtime Library from Borland as a starting point. I
ordered the Runtime Library several months into the project that
stimulated the creation of this system, but I didn't really need
it.
I was able to compile and debug over 90% of my code (which
was about 3,500 lines of C) using the Integrated Environment on a
PC, then re-compile (with #DEFINES and #IFDEFs to accommodate the
environmental differences) for ROM with little further debugging.
SEGMENT USAGE IN TURBO C:
Turbo C assigns uninitialized static variables to a segment
called _BSS and initialized static variables (i.e., constants) to
a segment called _DATA. These two segments are associated via a
GROUP directive, and assumed, under normal circumstances, to be
jointly addressable by the DS register. Instructions are
generated in the _TEXT segment, and assumed addressable by the CS
register.
In the ROM environment, the constants in the _DATA segment
and the code in the _TEXT segment are both located in the ROM and
addressed by CS. Only BSS is addressed by DS. Therefore to
convert the code generated by the compiler, ROMIZE is used as
follows:
WHAT ROMIZE DOES:
ROMIZE makes two passes through the assembly source code
generated by the command line compiler. In the first pass, it
reads the code and accumulates a table of all symbols defined in
the _DATA segment. There are only a few such symbols, since
Turbo C places all string constants in a single symbol "s@", and
refers to individual strings as "s@+n". Likewise, all
non-string constants are part of a single symbol called "d@" and
referred to by their displacements. There are however, a large
number of references to these symbols in the code.
In its second pass, ROMIZE re-reads the source code and
writes it out with modifications. The modifications are:
1. The command that groups _BSS and DATA together into DGROUP
is changed so that only _BSS is in DGROUP.
2. A new GROUP command is inserted creating CGROUP as _TEXT
and _DATA.
3. The ASSUME statement is changed from
ASSUME cs:_TEXT,ds:DGROUP
to:
ASSUME cs:CGROUP,ds:DGROUP
4. In any line of code with a reference to a symbol in the
_DATA segment, the group reference is changed from DGROUP to
CGROUP, or a CGROUP reference is added if no group name is
present.
5. Preceding a line of code with a reference to a symbol in
the _DATA segment may be a "mov x,ds" or a "push ds"instruction.
This is changed to a "mov x,cs" or "push cs".
6. Following a line of code with a reference to a symbol in
the _DATA segment may be a "push ds" instruction. This is changed
to a "push cs".
In order to do the looking backward and forward, ROMIZE
maintains a three-line pipeline, reading new lines into the head
of the pipe and writing lines from the end.
The various segment naming options of the compiler do not
produce the desired changes for the ROM environment. I would be
happy to pursue this subject further with anyone who is curious
about it.
LINKING:
You can use either TLINK or MS-LINK. The command line will,
of course be different for each. The "Warning - no stack segment"
will be produced by either. This is normal.
You must read the link map carefully to verify that your code
fit in the size of ROM you use. Consider the following partial
link map:
LINK : warning L4021: no stack segment
Start Stop Length Name Class
00000H 05A74H 05A75H _TEXT CODE
05A80H 07DF7H 02378H _DATA DATA
07DF8H 0A530H 02739H _BSS BSS
0A531H 0A531H 00000H _BSSEND BSSEND
Origin Group
0000:0 CGROUP
07DF:0 DGROUP
The .bin file that came out of EXE2BIN for the module that
produced this link map was A531 (hex) bytes long, however, this
module does fit in 32K. The key indicator is the sum of the
lengths of _TEXT and _DATA, and consequently, the starting
address of DGROUP (_BSS). Here it is 7DF8, about 500 bytes
under 32K.
MORE ABOUT EXE2BIN:
EXE2BIN is needed to convert the .EXE file into a binary
image that can be transmitted to an EPROM programmer. It may need
to perform "segment fixups" (segment relocation), if your program
includes constructs like the following:
static char *menu[] = {"Preview ","Add Before","Add After "};
This is a convenient way to generate arrays of strings for
such things as menus. This generates an array of pointers whose
segment parts must be relocated by EXE2BIN to the segment address
of the ROM. Therefore, when the EXE2BIN step is run, you will be
prompted to enter a segment address (F800 hex in